<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebAuthn 案例</title>
    <!-- 引入 Axios 库 -->
    <script src="./axios.min.js"></script>
</head>
<body>
    <div style="margin: 0 auto; width: 350px; text-align: center;">
        <h3>WebAuthn 案例</h3>
        <input type="text" id="username" placeholder="用户名" class="form-control" style="width: calc(88% - 80px); margin-bottom: 15px;">
        <div style="display: flex; justify-content: center; align-items: center;">
            <button id="btn-log" onmouseover="this.style.backgroundColor='#0056b3'" onmouseleave="this.style.backgroundColor='#007bff'" style="padding: 10px 40px; border: none; background-color: #007bff; color: #fff; cursor: pointer; margin-right: 10px;">登录</button>
            <button id="btn-reg" onmouseover="this.style.backgroundColor='#c82333'" onmouseleave="this.style.backgroundColor='#dc3545'" style="padding: 10px 40px; border: none; background-color: #dc3545; color: #fff; cursor: pointer; margin-left: 10px;">注册</button>
        </div>
    </div>

    <script>
        const baseURL = 'http://localhost:8080/webauthn';

        // 注册按钮点击事件
        document.getElementById('btn-reg').addEventListener('click', function () {
            // 获取用户名输入框的值
            const val = document.getElementById('username').value;
            // 如果值为空，则返回
            if (!val) return;
            // 发起注册请求
            register({ userName: val, displayName: val }, res => {
                // 如果注册成功
                if (res.code === 200) {
                    // 解析注册响应数据
                    const credentialCreateJson = JSON.parse(res.data);
                    // 创建 WebAuthn 凭证选项对象
                    const credentialCreateOptions = {
                        publicKey: {
                            ...credentialCreateJson.publicKey,
                            challenge: base64urlToUint8array(credentialCreateJson.publicKey.challenge),
                            user: { ...credentialCreateJson.publicKey.user, id: base64urlToUint8array(credentialCreateJson.publicKey.user.id) },
                            excludeCredentials: credentialCreateJson.publicKey.excludeCredentials.map(credential => ({
                                ...credential,
                                id: base64urlToUint8array(credential.id),
                            })),
                            extensions: credentialCreateJson.publicKey.extensions,
                        }
                    };
                    // 获取页面焦点
                    window.focus();
                    // 调用 WebAuthn API 创建凭证
                    navigator.credentials.create(credentialCreateOptions)
                        .then(publicKeyCredential => ({
                            id: publicKeyCredential.id,
                            type: publicKeyCredential.type,
                            response: {
                                attestationObject: uint8arrayToBase64url(publicKeyCredential.response.attestationObject),
                                clientDataJSON: uint8arrayToBase64url(publicKeyCredential.response.clientDataJSON),
                                transports: publicKeyCredential.response.getTransports && publicKeyCredential.response.getTransports() || [],
                            },
                            clientExtensionResults: publicKeyCredential.getClientExtensionResults(),
                        }))
                        .then(encodedResult => {
                            // 发送注册认证请求
                            const param = { userName: val, credential: JSON.stringify(encodedResult) };
                            registerAuth(param, res => {
                                if (res.code === 200) {
                                    // 提示注册成功
                                    alert('注册成功');
                                } else {
                                    // 提示注册失败
                                    alert(res.data);
                                }
                            });
                        });
                }
            });
        });

        // 登录按钮点击事件
        document.getElementById('btn-log').addEventListener('click', function () {
            // 获取用户名输入框的值
            const val = document.getElementById('username').value;
            // 如果值为空，则返回
            if (!val) return;
            // 发起登录请求
            login({ userName: val }, res => {
                // 如果登录成功
                if (res.code !== 200) {
                    // 提示登录失败
                    alert(res.data);
                    return;
                }
                // 解析登录响应数据
                const credentialGetJson = JSON.parse(res.data);
                // 创建 WebAuthn 凭证选项对象
                const credentialGetOptions = {
                    publicKey: {
                        ...credentialGetJson.publicKey,
                        allowCredentials: credentialGetJson.publicKey.allowCredentials && credentialGetJson.publicKey.allowCredentials.map(credential => ({
                            ...credential,
                            id: base64urlToUint8array(credential.id),
                        })),
                        challenge: base64urlToUint8array(credentialGetJson.publicKey.challenge),
                        extensions: credentialGetJson.publicKey.extensions,
                    },
                };
                // 调用 WebAuthn API 获取凭证
                navigator.credentials.get(credentialGetOptions)
                    .then(publicKeyCredential => ({
                        type: publicKeyCredential.type,
                        id: publicKeyCredential.id,
                        response: {
                            authenticatorData: uint8arrayToBase64url(publicKeyCredential.response.authenticatorData),
                            clientDataJSON: uint8arrayToBase64url(publicKeyCredential.response.clientDataJSON),
                            signature: uint8arrayToBase64url(publicKeyCredential.response.signature),
                            userHandle: publicKeyCredential.response.userHandle && uint8arrayToBase64url(publicKeyCredential.response.userHandle),
                        },
                        clientExtensionResults: publicKeyCredential.getClientExtensionResults(),
                    }))
                    .then(encodedResult => {
                        // 发送完成登录请求
                        const params = { userName: val, credential: JSON.stringify(encodedResult) };
                        finishLogin(params, res => {
                            if (res.code === 200) {
                                // 提示认证成功
                                alert('认证成功');
                                // 重定向到百度首页
                                top.location = 'https://www.baidu.com/';
                            } else {
                                // 提示认证失败
                                alert(res.msg);
                            }
                        });
                    });
            });
        });

        // 注册函数
        function register(data, callback) {
            // 发起注册请求
            axios.post(`${baseURL}/register`, data)
                .then(response => callback(response.data))
                .catch(error => console.error(error));
        }

        // 注册认证函数
        function registerAuth(data, callback) {
            // 发起注册认证请求
            axios.post(`${baseURL}/registerAuth`, data)
                .then(response => callback(response.data))
                .catch(error => console.error(error));
        }

        // 登录函数
        function login(data, callback) {
            // 发起登录请求
            axios.post(`${baseURL}/login`, data)
                .then(response => callback(response.data))
                .catch(error => console.error(error));
        }

        // 完成登录函数
        function finishLogin(data, callback) {
            // 发起完成登录请求
            axios.post(`${baseURL}/finishLogin`, data)
                .then(response => callback(response.data))
                .catch(error => console.error(error));
        }

        // 将 base64url 字符串转换为 Uint8Array
        function base64urlToUint8array(base64Bytes) {
            // 计算填充长度
            const padding = '===='.substring(0, (4 - (base64Bytes.length % 4)) % 4);
            // 替换 '-' 为 '+', '_' 为 '/'，并添加填充
            const base64 = (base64Bytes + padding).replace(/\-/g, '+').replace(/_/g, '/');
            // 解码 base64 字符串为原始二进制数据
            const rawData = atob(base64);
            // 创建一个与 rawData 长度相等的 Uint8Array 数组
            const uint8Array = new Uint8Array(rawData.length);
            // 遍历 rawData，并将每个字符的 Unicode 编码值存入 uint8Array
            for (let i = 0; i < rawData.length; ++i) {
                uint8Array[i] = rawData.charCodeAt(i);
            }
            // 返回转换后的 Uint8Array
            return uint8Array;
        }

        // 将 Uint8Array 转换为 base64url 字符串
        function uint8arrayToBase64url(bytes) {
            // 使用 Uint8Array 创建一个数组缓冲区
            const buffer = new Uint8Array(bytes);
            // 使用 String.fromCharCode 将字节转换为 Unicode 字符
            const charArray = Array.from(buffer, byte => String.fromCharCode(byte));
            // 使用 Array.join 将 Unicode 字符拼接为字符串
            const rawString = charArray.join('');
            // 使用 btoa 将原始字符串进行 base64 编码
            const base64 = btoa(rawString);
            // 替换 base64 编码中的字符 '+' 和 '/'
            const base64url = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
            return base64url;
        }

    </script>

</body>
</html>
